package random;

import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import java.util.Arrays;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import util.*;
import GA.MethodDeclared;
import GA.ConstructorDeclared;
import Operations.ValuesGenerator;
public class TestClusterBuilder {
	private final List<String> classesToTest;
	private final Map<String, Class<?>> cluster;
	private final Map<String, String[]>  refFactory;
	private final Map<String, ConstructorDeclared[]> classToTest;
	private final Map<String, MethodDeclared[]> methodsToTest;
	public TestClusterBuilder( List<String> classesToTest)throws ClassNotFoundException{
		this.classesToTest=classesToTest;
		cluster = new LinkedHashMap<String, Class<?>>();
		refFactory= new LinkedHashMap<String, String[]>();
		methodsToTest= new LinkedHashMap<String, MethodDeclared[]>();
		classToTest= new LinkedHashMap<String, ConstructorDeclared[]>();
		calculateCluster(this.classesToTest);
		// Create a reference  for each clazz 
		 createRefMap(cluster);
		 
		// calculate constructors, methods, and assignableTo
		reflection(cluster,this.classesToTest);



	}
	

	public  Map<String, Class<?>>  getCluster(){
		return this.cluster;		
	}
	public  Map<String, ConstructorDeclared[]>  getClassToTest(){
		return this.classToTest;		
	}

	public  Map<String, String[]>   getRefFactory(){
		return this.refFactory;		
	}
	public  Map<String, MethodDeclared[]>  getMethodsToTest(){
		return this.methodsToTest;		
	}

	private void calculateCluster(List<String>  cutClasses) throws ClassNotFoundException {
		Set<String> toDo = new HashSet<String>();
		toDo.addAll(cutClasses);
		while(!toDo.isEmpty()) {
			String className = toDo.iterator().next();
			toDo.remove(className);

			if(cluster.get(className) == null) {
				System.out.println("   TestClusterBuilder.calculateCluster Class: " + className);
			Class<?> javaClass = Class.forName(Filter.PrimitiveGenericConverter(className));
			if (!Filter.canUse(javaClass)) continue;
				cluster.put(className,javaClass);
				if(javaClass.getName().startsWith("java.")) continue;
				
				// Include types used in public fields
				for(Field f : javaClass.getFields()) {
					if(toDo.contains(f.getType().getName())) continue;
						toDo.add(f.getType().getName());
				}
				for(Constructor<?> cns : javaClass.getConstructors()) {
					System.out.printf ("Considering Constructor %s%n", cns);
						for(Class<?> param : cns.getParameterTypes()){
							if (!Filter.canUse(param)) continue;
							toDo.add(param.getName());
						}
				}
				// Consider methods
				for(Method meth : javaClass.getDeclaredMethods()) {
					 System.out.printf ("calculateCluster Considering method %s%n", meth);
//					if(Modifier.isPrivate(meth.getModifiers())) continue;
						if(Filter.canUse(meth)){
							for(Class<?> param : meth.getParameterTypes())
								toDo.add(param.getName());
						// toDo.add(meth.getReturnType().getName());
						}
				}

			}

		}
	}

	private void reflection(Map<String, Class<?>> cluster,List<String>  cutClasses)throws ClassNotFoundException{
		for( Entry<String, Class<?>> c : cluster.entrySet()) {
			 Class<?> clazz = Class.forName(c.getValue().getName().toString());
			if(!Filter.isPrimitiveOrStringType(clazz)&& !Filter.isObjectType(clazz)&&Filter.canUse(c.getValue())&&!Modifier.isInterface(clazz.getModifiers())) {
				System.out.println("TestClusterBuilder.reflection  clazzToTest: "+clazz);
					if(cutClasses.contains(clazz.getName()))
						calculateMethods(clazz);		
					calculateConstructors(clazz);
			}
		}
		}
	private void calculateMethods(Class<?> javaClass) {
		Set<MethodDeclared> mlist =  new HashSet<MethodDeclared>();
		for(Method meth : javaClass.getDeclaredMethods()) {
				if(Modifier.isPrivate(meth.getModifiers())) continue;
				if(!Filter.canUse(meth)) continue;
				System.out.println("\t TestClusterBuilder.calculateMethods  methodToTest: "+meth);
			
				Class<?> returnType;
				if(meth.getReturnType() ==  Void.TYPE || meth.getReturnType().isArray() || meth.getReturnType().isEnum())
					returnType = null;
				else
					returnType = meth.getReturnType();

				mlist.add(new MethodDeclared(javaClass, meth.getName(),
						Modifier.isStatic(meth.getModifiers()), meth.getParameterTypes(),
						returnType,null,null));
			}
		MethodDeclared[] methods = mlist.toArray(new MethodDeclared[mlist.size()]);

	     Arrays.sort(methods);

		methodsToTest.put(javaClass.getName(), methods);
		}
	private void calculateConstructors(Class<?> javaClass) {
		
		Set<ConstructorDeclared> clist =  new HashSet<ConstructorDeclared>();
		List<String> refKeys = new ArrayList<String>(refFactory.keySet()); 
		for(int refIndx=0;refIndx<refKeys.size();refIndx++){
			String cName =(String) refKeys.get(refIndx);
			
			if(!cName.equals(javaClass.getName())) continue;
			
			String[] refrences = refFactory.get(cName);
			Constructor[] cnsList=javaClass.getConstructors();
			for (int i = 0; i < refrences.length && cnsList.length!=0; i++) {
				String classRefrence = refrences[i].toString();

				int j=i;
				if(i>=cnsList.length)
					j=0;
				Constructor<?> cns= (Constructor<?>) cnsList[j];
				Class<?>[] paramsType = cns.getParameterTypes();
				Object[] actualParams = new Object[cns.getParameterTypes().length];
				String[] params = new String[paramsType.length];
				//calculateConstants
				List<Field> constantsList= new ArrayList<Field>();
				Field[] constants = javaClass.getFields();
				for(Field field : constants) {
					if(Filter.skip(field)) continue;
						constantsList.add(field);
				}
				clist.add(new ConstructorDeclared(classRefrence,javaClass,params, cns.getParameterTypes(),constantsList,cns));

			}
			ConstructorDeclared[] constructors = clist.toArray(new ConstructorDeclared[refrences.length]);
			if(clist.size()!=0)
				Arrays.sort(constructors);
		
			classToTest.put(javaClass.getName(), constructors);

		}
	}

	private void createRefMap(Map<String, Class<?>> cluster) {
	//	refMap = new HashMap<Clazz, Reference[]>();
		for( Entry<String, Class<?>> c : cluster.entrySet()) {
			String[] refs = new String[GAConfig.NumVarCut];
			//  System.out.println("   create Refrence Class: " + c.getKey());
			for(int i = 0; i < refs.length; i++)
				if(this.classesToTest.contains(c.getKey()))
					refs[i] = c.getKey().substring(c.getKey().lastIndexOf(".") + 1)+ "_" + i;
				else
				refs[i] = c.getKey().replace('.', '_') + "_" + i;

			refFactory.put(c.getKey(), refs);
		}
	}
	public  String getReference(String cName,Map<String, String[]>  refFactory) {		
		String refs[] = refFactory.get(cName);
		if(refs == null) return null;
		return refs[ValuesGenerator.getRandomInt(refs.length)];
	}


}
